/**
 * 
 */
package edu.fiu.cis.selfhealingdisk;

import java.util.Collections;
import java.util.HashMap;

import edu.fiu.cis.selfhealingdisk.IORequest.IOEnum;

/**
 * @author eruiz
 * 
 */
public class BufferedParityModel extends ParityModel {

	private final SHMemory memory;

	public static int calcExtraBlocksPerGroup(long totalParityGroups,
			long parityGroupSize) {
		return 0;
	}

	protected BufferedParityModel(IOTrace originalTrace,
			DiskLayout originalLayout, int memorySize,  int clt,
			long maxMemTime) {
		super(originalTrace, originalLayout);
		System.out.println(memorySize + " " + clt + " " + maxMemTime);
		this.memory = new SHMemory(memorySize, clt, maxMemTime);
	}

	int c = 0;

	@Override
	public Event nextEvent(long currentTime) {

		IORequest or = null;
		IORequest tr = null;
		IORequestList requests = new IORequestList();

		// Check if there is not event pending in any queue
		if (((or = this.originalTrace.peek()) == null)
				&& (memory.isMemoryClear())) {
			
			return Event.createDoneEvent();
		}

		// This method simulate the incoming read and write events that are
		// executed in the parity buffer
		checkInterruptions(currentTime);

		// Clean the memory
		this.memory.cleanMemory(currentTime,requests);
		

		// Get all trace events before the t[i+1] time
		while ((or = this.originalTrace.peek()) != null
				&& or.getRequestTime() <= currentTime) {
			// Move the iterator
			this.originalTrace.next();

			// Create the new trace entry using the old entry
			tr = new IORequest(or.getRequestTime(), this.originalLayout
					.calcPhysicalBlock(or.getLba()), or.getSize(),
					or.getType(), false);

			if (or.getType().equals(IOEnum.READ)) {
				// All read requests are sent as soon as possible to the disk
				requests.add(tr);
			} else {
				long parityBlock = this.originalLayout.calcParityBlock(tr
						.getLba());
				if (!memory.blockRequest(parityBlock, tr)) {
					addDataWriteRequest(tr, requests);
				}
			}

		}
		
		for (IORequest ior: requests) {
			this.transformedTrace.addIORequest(ior);
		}

		if (!requests.isEmpty()) {
			Collections.sort(requests);
			// Case II: We have request for this window. Sent requests in time
			// [ti,ti+1]
			return Event.createIOEvent(requests);
		} else {
			if (or != null && this.memory.isMemoryClear()) {
				// Case III: No Pending requests in time [ti,ti+1] and not
				// waiting events
				// We can warp to the next request

				return Event.createWarpEvent(or.getRequestTime());
			}
			// Case IV: No Pending requests in time [ti,ti+1] and some events
			// are waiting. Need to wait for disk answer
			return Event.createIOEvent(requests);
		}

	}

	
	/**
	 * Check the interruption queue and change the status of the memory depending of the new events
	 * The vector simulate all the interruptions that have entered in the last window of time
	 * The interruption queue is shared between the simulator and the parity model so we can update the events 
	 * from both classes
	 * @param timestamp
	 */
	public void checkInterruptions(long timestamp) {
		if (this.pendingInterruptions.isEmpty())
			return;

		IORequest ior = null;
		while ((ior = this.pendingInterruptions.poll()) != null) {
			
			if (ior.getType().equals(IOEnum.READ)) {
				//Establish that the memory is ready for read and simulate a parity change 
				this.memory.readParityComplete(ior.getLba(), timestamp);
				this.memory.writeParityChange(ior.getLba(), timestamp);
			} else if (ior.getType().equals(IOEnum.WRITE)) {
				//Clear the parity change for this memory location
				this.memory.clearParityChange(ior.getLba(), timestamp);
			}
		}
	}

	public void addDataWriteRequest(IORequest tr, IORequestList requests) {
		requests.add(tr);

		// Calculate the parity block
		long parityBlock = this.originalLayout.calcParityBlock(tr.getLba());

		// Reserve the memory for reading
		boolean addedEntry = this.memory.addEntry(parityBlock, tr.getSize(), tr.getRequestTime());

		if (addedEntry) {
			// The parity was not in memory (we must add the request and wait
			// for read finalization)
			// Force the read for this parity
			IORequest pr = new IORequest(tr.getRequestTime() + 1, parityBlock,
					tr.getSize(), IOEnum.READ, true);
			requests.add(pr);
		} else {
			// The block was in memory and completely loaded!!
			this.memory.writeParityChange(parityBlock, tr.getRequestTime());
		}
	}

	
	public HashMap<String,String> getStats() {
		HashMap<String,String> stats = new HashMap<String,String>(); 
		stats.put("Memory Hits",Integer.toString(this.memory.getMemoryHits()));
		stats.put("Number of Flushes",Integer.toString(this.memory.cleanOperationsCount()));
		stats.put("Max Memory Usage",Integer.toString(this.memory.getMaxMem()));
		return stats;
	}
}
